package deploy

import (
	"fmt"
	appsv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"log"
	"strings"
	"time"
)

const ClusterVersion = "v1.16.9"

type k8sSvc struct {
	cliSet *kubernetes.Clientset
}

func NewK8sSvc() (*k8sSvc, error) {
	cli, err := newK8sCliSet()
	if err != nil {
		return nil, fmt.Errorf("k8s cli 初始化失败, %s", err.Error())
	}
	return &k8sSvc{cliSet: cli}, nil
}

func newK8sCliSet() (*kubernetes.Clientset, error) {
	config, err := clientcmd.BuildConfigFromFlags("", Kubeconfig)
	if err != nil {
		return nil, err
	}
	cli, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	version, err := cli.ServerVersion()
	if err != nil {
		return nil, err
	}
	if version.GitVersion != ClusterVersion {
		return nil, fmt.Errorf("此程序仅适用于 k8s %s, 当前版本 %s\n", ClusterVersion, version.GitVersion)
	}
	return cli, nil
}

func (svc *k8sSvc) RestartDeployment(ns, group, image string) error {
	deployCli := svc.cliSet.AppsV1().Deployments(ns)
	deploy, err := deployCli.Get(group, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("集群中获取 deploymnet %s 失败, %s\n", group, err.Error())
	}
	num := *deploy.Spec.Replicas
	if num == 0 {
		num = 1
	}
	// 先缩到 0
	if err := svc.DeployScale(ns, group, 0, "", 0); err != nil {
		return fmt.Errorf("部署组 %s 缩容到 0 发生错误，%s\n", group, err.Error())
	}
	log.Printf("Deployment %s Scale --replicas=0", group)

	// 再扩到原来的数目并更新镜像
	if err := svc.DeployScale(ns, group, num, image,0); err != nil {
		return fmt.Errorf("部署组 %s 扩容到 %d 发生错误，%s\n", group, num, err.Error())
	}
	log.Printf("Deployment %s Scale --replicas=%d --image=%s\n", group, num, image)

	svc.WatchIsReady(ns, group, num)
	return nil
}

// DeployScale 自旋 Update
func (svc *k8sSvc) DeployScale(ns string, group string, num int32, image string, try int) error {
	deploy, err := svc.cliSet.AppsV1().Deployments(ns).Get(group, metav1.GetOptions{})
	if err != nil {
		return err
	}
	if image != "" {
		setNewImage(deploy, image)
	}
	deploy.Spec.Replicas = &num
	if _, err := svc.cliSet.AppsV1().Deployments(ns).Update(deploy); err != nil {
		if strings.Contains(err.Error(), "try again") && try < 3{
			fmt.Println(err.Error())
			fmt.Println("wait 10s ...")
			time.Sleep(10 * time.Second)
			try++
			svc.DeployScale(ns, group, num, image, try)
		} else {
			return err
		}
	}
	return nil
}

func (svc *k8sSvc) WatchIsReady(ns, group string, expect int32) {
	var ready int32 = 0
	for {
		deploy, err := svc.cliSet.AppsV1().Deployments(ns).Get(group, metav1.GetOptions{})
		if err != nil {
			log.Printf("wath err, %s\n", err.Error())
			return
		}

		if ready == deploy.Status.ReadyReplicas {
			continue
		}

		ready = deploy.Status.ReadyReplicas
		log.Printf("部署组 %s 总计需要运行 %d 个 pod，已运行 %d 个 pod\n", group, expect, ready)

		if  ready == expect {
			break
		}
		time.Sleep(3 * time.Second)
	}
}


func getTSFGroupID(deployment *appsv1.Deployment) string {
	return deployment.Labels["qcloud-app"]
}

func setNewImage(deployment *appsv1.Deployment, image string) {
	groupID := getTSFGroupID(deployment)
	for i, c := range deployment.Spec.Template.Spec.Containers {
		if c.Name == groupID + "-docker" {
			deployment.Spec.Template.Spec.Containers[i].Image = image
		}
	}
}